/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.instruction;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;

import net.paoding.rose.Rc;
import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.Invocation;
import net.paoding.rose.web.impl.thread.ControllerEngine;
import net.paoding.rose.web.impl.thread.InvocationBean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

/**
 * 
 * @author zhiliang.wang
 * 
 */
public class InstructionExecutorImpl implements InstructionExecutor {

	private Log logger = LogFactory.getLog(getClass());

	@Override
	public Object render(Invocation invocation, Object instruction) throws IOException, ServletException, Exception {
		instruction = translatesToInstructionObject((InvocationBean) invocation, instruction);
		((Instruction) instruction).render(invocation);
		return instruction;
	}
	
	/**
	 * 
	 * @param invocation
	 * @param instruction
	 * @return
	 * @throws StackOverflowError
	 */
	private Instruction translatesToInstructionObject(InvocationBean invocation, Object instruction) throws StackOverflowError {
		int count = 0;
		while (!(instruction instanceof Instruction)) {
			if (count++ > 50) {
				throw new StackOverflowError("Unable to parse the instruction to an"
						+ " Instruction object less than " + count
						+ " times. Is the instruction" + " that returned by your controller"
						+ " action is right?");
			}
			
			if (instruction == null
					&& invocation.getMethod().getReturnType() != void.class) {
				break;
			} else if (instruction == null || instruction.equals(">")
					|| instruction.equals('>')) {
				ControllerEngine controllerEngine = invocation.getControllerEngine();
				String viewPrefix = controllerEngine.getViewPrefix();
				instruction = View.name(viewPrefix + invocation.getMethod().getName());
			} else {
				if (instruction.getClass() != String.class
						&& !ClassUtils.isPrimitiveOrWrapper(instruction.getClass())
						&& instruction.getClass().getComponentType() == null
						&& instruction.getClass().getAnnotation(Component.class) != null) {
					SpringUtils.autowire(instruction, invocation.getRequestPath().getModulePath());
				}
				instruction = parseInstruction(instruction);
			}
		}
		return (Instruction) instruction;
	}
	
	
	protected Object parseInstruction(Object instruction) {
		if (logger.isDebugEnabled()) {
			logger.debug("parset instruction:" + instruction.getClass().getName() + ": '"
					+ instruction + "'");
		}
		if (ClassUtils.isPrimitiveOrWrapper(instruction.getClass())) {
			return Text.plain(instruction);
		} else if (instruction instanceof CharSequence) {
			String str = instruction.toString();
			if (str.length() == 0) {
				return Text.plain("");
			}
			if (str.charAt(0) == '@') {
				return Text.html(str.substring(1)); // 使用html而非plain，如有需要可以通过@
				// HttpFeatures进行设置更改
			}
			if (str.charAt(0) == '/') {
				return View.name(str);
			}
			if (str.startsWith("f:") || str.startsWith("forward:")) {
				return new StringInstruction(true, str.substring(str.indexOf(':') + 1));
			}
			if (str.startsWith("r:") || str.startsWith("redirect:")) {
				StringInstruction si = new StringInstruction(false, str
						.substring(str.indexOf(':') + 1));
				if (si.innerInstruction.startsWith("http://")
						|| si.innerInstruction.startsWith("https://")) {
					return Redirect.location(si.innerInstruction);
				}
				return si;
			}
			if (str.startsWith("s:") || str.startsWith("status:")) {
				int index = str.indexOf(':') + 1;
				int other = str.indexOf(';', index);
				if (other == -1) {
					Rc.response().setStatus(Integer.parseInt(str.substring(index)));
					return null;
				} else {
					Rc.response().setStatus(Integer.parseInt(str.substring(index, other)));
					for (int i = other; i < str.length(); i++) {
						if (str.charAt(i) != ' ') {
							str = str.substring(i + 1);
							break;
						}
					}
				}
			} else if (str.startsWith("e:") || str.startsWith("error:")) {
				int msgIndex = str.indexOf(';');
				if (msgIndex == -1) {
					return new HttpErrorInstruction(Integer.parseInt(str
							.substring(str.indexOf(':') + 1)));
				} else {
					return new HttpErrorInstruction(Integer.parseInt(str.substring(
							str.indexOf(':') + 1, msgIndex)), str.substring(msgIndex + 1));
				}
			}
			return new StringInstruction(null, str);
		} else if (instruction.getClass() == StringInstruction.class) {
			StringInstruction fr = (StringInstruction) instruction;
			String str = fr.innerInstruction;
			int queryIndex = str.indexOf('?');
			for (int i = (queryIndex == -1) ? str.length() - 1 : queryIndex - 1; i >= 0; i--) {
				if (str.charAt(i) != ':') {
					continue;
				}
				if (i > 0 && str.charAt(i - 1) == '\\') { // 转义符号
					str = str.substring(0, i - 1) + str.substring(i);
					i--;
					continue;
				}
				int cmdEnd = i;
				int cmdBeforeBegin = i - 1;
				while (cmdBeforeBegin >= 0 && str.charAt(cmdBeforeBegin) != ':') {
					cmdBeforeBegin--;
				}
				String prefix = str.subSequence(cmdBeforeBegin + 1, cmdEnd).toString();
				String body = str.subSequence(i + 1, str.length()).toString();
				if ("a".equals(prefix) || "action".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.action(body);
					} else {
						return Forward.action(body);
					}
				}
				if ("c".equals(prefix) || "controller".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.controller(body);
					} else {
						return Forward.controller(body);
					}
				}
				if ("m".equals(prefix) || "module".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.module(body);
					} else {
						return Forward.module(body);
					}
				}
				logger.warn("skip the prefix '" + prefix + ":' of " + str);
				if (fr.isReirect()) {
					return Redirect.location(str);
				} else if (fr.isForward()) {
					return Forward.path(str);
				} else {
					return View.name(str);
				}
			}
			if (fr.isReirect()) {
				return Redirect.location(str);
			} else if (fr.isForward()) {
				return Forward.path(str);
			}
			return View.name(str);
		}
		else if (instruction instanceof InputStream) {
			return new InputStreamInstruction((InputStream) instruction);
		}
		else if (instruction instanceof byte[]) {
			return new InputStreamInstruction(new ByteArrayInputStream((byte[]) instruction));
		}
		else {
			return Text.plain(instruction.toString());
		}
	}


	private class StringInstruction {
		Boolean forward;
		String innerInstruction;

		public StringInstruction(Boolean forward, String innerInstruction) {
			super();
			this.forward = forward;
			this.innerInstruction = innerInstruction;
		}

		public boolean isReirect() {
			return forward != null && !forward.booleanValue();
		}

		public boolean isForward() {
			return forward != null && forward.booleanValue();
		}

		@Override
		public String toString() {
			return forward == null ? "" : (forward ? "f:" : "r:") + innerInstruction;
		}
	}

}
